/*
 * Copyright (c) 2017. Visionet and/or its affiliates. All right reserved.
 * VISIONET PROPRIETARY/CONFIDENTIAL.
 */
package com.visionet.jumper.core.support.jpa;

import com.visionet.jumper.core.exception.runtime.FrameworkException;
import freemarker.cache.StringTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.Version;
import org.apache.commons.lang3.StringUtils;
import org.jdom2.Document;
import org.jdom2.Element;
import org.jdom2.input.SAXBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author TC.Ubuntu
 * @since 2017/12/26.
 */
@Component
public class SqlReaderFactory implements ApplicationContextAware {

	private static Logger logger = LoggerFactory.getLogger(SqlReaderFactory.class);

	private static Map<String, StringBuffer> sqlStatement = new HashMap<>();

	private static void setSqlResource() {
		InputStream inputStream = null;
		Document document;
		SAXBuilder saxBuilder;
		try {
			//resolve resource file
			PathMatchingResourcePatternResolver patternResolver = new PathMatchingResourcePatternResolver();
			Resource[] resources = patternResolver.getResources("classpath*:query/*.query.xml");
			for(Resource resource : resources) {
				if(logger.isInfoEnabled()) {
					logger.info("Loading SQL definitions from class path resource [{}]", resource.getURI());
				}
				inputStream = resource.getInputStream();
				saxBuilder = new SAXBuilder();
				document = saxBuilder.build(inputStream);
				Element element =  document.getRootElement();
				List<Element> elements = element.getChildren();
				String sqlName;
				for(Element elementChild : elements) {
					sqlName = elementChild.getAttribute("name").getValue();
					if(StringUtils.isBlank(sqlName)) {
						throw new FrameworkException("A name can not be empty for a SQL defination!");
					}
					if(sqlStatement.containsKey(sqlName)) {
						throw new FrameworkException(String.format("SQL defination expect an unique name "
										+ "but found more than one, conflicting name = [%s] defined in file [%s]",
								sqlName, resource.getURL()));
					}
					sqlStatement.put(sqlName, new StringBuffer(elementChild.getValue().replaceAll("\n", "").trim()));
				}
			}
		} catch (Exception e) {
			throw new FrameworkException("Load SQL resource failed!", e);
		} finally {
			if(inputStream != null) {
				try {
					inputStream.close();
				} catch (IOException e) {
					logger.error("Close inputstream failed while SQL defination has been loaded!");
				}
			}
		}
	}

	protected static String getSqlResource(Map<String, Object> dataModel, String name) {
		Assert.hasText(name, "SQL name can not be null or blank");
		String sqlResource = sqlStatement.get(name).toString();
		if(StringUtils.isBlank(sqlResource)) {
			throw new FrameworkException(String.format("SQL for name [%s] is not defined yet!", name));
		}
		try {
			Configuration configuration = new Configuration(new Version("2.3.0"));
			configuration.setNumberFormat("#");
			StringTemplateLoader stringLoader = new StringTemplateLoader();
			Template template = new Template(name, new StringReader(sqlResource), configuration);
			configuration.setTemplateLoader(stringLoader);
			StringWriter sw = new StringWriter(100);
			template.process(dataModel, sw);
			return sw.toString();
		} catch (IOException e) {
			throw new FrameworkException(String.format("Failed to parse sqlResource named [%s] while reading resource",
					name), e);
		} catch (TemplateException e) {
			throw new FrameworkException(String.format("Failed to parse sqlResource named [%s] cause a template error",
					name), e);
		}
	}

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		setSqlResource();
	}
}
